#include "VulkanApplication.h"
#include <vector>
#include <iostream>
#include <fstream>

// glslc -fshader-stage=vert xxx.vert.glsl -o xxxx.vert
// glslc -fshader-stage=frag xxx.vert.glsl -o xxxx.frag
VkShaderModule createShaderModule(VulkanApplication& app, const std::string& file)
{
#ifdef _WIN32
    std::ifstream fin("../" + file, std::ios::in | std::ios::binary);
#else
    std::ifstream fin(file, std::ios::in | std::ios::binary);
#endif
    std::string buffer((std::istreambuf_iterator<char>(fin)), std::istreambuf_iterator<char>());

    VkShaderModuleCreateInfo createInfo
    {
        VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
        nullptr, 0,
        buffer.size(), (uint32_t*)buffer.data()
    };

    VkShaderModule shaderModule = nullptr;
    VkResult result = vkCreateShaderModule(app.getDevice(), &createInfo, nullptr, &shaderModule);
    if (result != VK_SUCCESS)
        printf("Failed to create shader module.\n");
    return shaderModule;
}

int main(int argc, char** argv)
{
    VulkanApplication app("c09_pipeline", 1024, 768);
    if (!app.initialized())
    {
        printf("Failed to initialize VulkanApplication.\n");
        return 1;
    }

    // Create graphics pipeline
    VkShaderModule vertShader = createShaderModule(app, "sample.vert");
    VkShaderModule fragShader = createShaderModule(app, "sample.frag");

    VkPipelineShaderStageCreateInfo shaderStages[] =
    {
        {
            VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
            nullptr, 0,
            VK_SHADER_STAGE_VERTEX_BIT, vertShader,
            "main", nullptr
        },
        {
            VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
            nullptr, 0,
            VK_SHADER_STAGE_FRAGMENT_BIT, fragShader,
            "main", nullptr
        }
    };

    VkPipelineVertexInputStateCreateInfo inputVertexInfo
    {
        VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
        nullptr, 0,
        0, nullptr,  // vertexBindingDescriptionCount
        0, nullptr   // vertexAttributeDescriptionCount
    };

    VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo
    {
        VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
        nullptr, 0,
        VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_FALSE
    };

    VkPipelineViewportStateCreateInfo viewportState
    {
        VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
        nullptr, 0,
        1, nullptr,  // viewportCount
        1, nullptr    // scissorCount
    };

    VkPipelineRasterizationStateCreateInfo rasterizer
    {
        VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
        nullptr, 0,
        VK_FALSE, VK_FALSE,    // depthClampEnable, rasterizerDiscardEnable
        VK_POLYGON_MODE_FILL,
        VK_CULL_MODE_BACK_BIT,
        VK_FRONT_FACE_CLOCKWISE,
        VK_FALSE, 0.0f, 0.0f, 0.0f, 1.0f
    };

    VkPipelineMultisampleStateCreateInfo multisampling
    {
        VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
        nullptr, 0,
        VK_SAMPLE_COUNT_1_BIT, VK_FALSE,
        0.0f, nullptr,        // minSampleShading, pSampleMask
        VK_FALSE, VK_FALSE    // alphaToCoverageEnable, alphaToOneEnable
    };

    VkPipelineColorBlendAttachmentState colorBlendAttachment
    {
        VK_FALSE,    // blendEnable
        VK_BLEND_FACTOR_ONE,
        VK_BLEND_FACTOR_ZERO,
        VK_BLEND_OP_ADD,       // colorBlendOp
        VK_BLEND_FACTOR_ONE,
        VK_BLEND_FACTOR_ZERO,
        VK_BLEND_OP_ADD,       // alphaBlendOp
        VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
        VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
    };

    VkPipelineColorBlendStateCreateInfo colorBlending
    {
        VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
        nullptr, 0,
        VK_FALSE, VK_LOGIC_OP_COPY,  // logicOp
        1, &colorBlendAttachment,
        { 0.0f, 0.0f, 0.0f, 0.0f }
    };

    std::vector<VkDynamicState> dynamicStates =
    {
        VK_DYNAMIC_STATE_VIEWPORT,
        VK_DYNAMIC_STATE_SCISSOR
    };
    VkPipelineDynamicStateCreateInfo dynamicState
    {
        VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
        nullptr, 0,
        (uint32_t)dynamicStates.size(), dynamicStates.data()
    };

    VkPipelineLayoutCreateInfo pipelineLayoutInfo
    {
        VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
        nullptr, 0,
        0, nullptr,
        0, nullptr
    };

    VkPipelineLayout pipelineLayout = nullptr;
    VkResult result = vkCreatePipelineLayout(
        app.getDevice(), &pipelineLayoutInfo, nullptr, &pipelineLayout);
    if (result != VK_SUCCESS)
    {
        printf("Failed to create pipeline layout.\n");
        return 1;
    }

    VkGraphicsPipelineCreateInfo pipelineInfo
    {
        VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
        nullptr, 0,
        2, shaderStages,  // pStages
        &inputVertexInfo,
        &inputAssemblyInfo,
        nullptr,  // pTessellationState
        &viewportState,
        &rasterizer,
        &multisampling,
        nullptr,  // pDepthStencilState,
        &colorBlending,
        &dynamicState,
        pipelineLayout,
        app.getRenderPass(),
        0,    // subpass
        VK_NULL_HANDLE, 0
    };

    VkPipeline pipeline = nullptr;
    result = vkCreateGraphicsPipelines(
        app.getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline);
    if (result != VK_SUCCESS)
    {
        printf("Failed to create pipeline.\n");
        return 1;
    }
    vkDestroyShaderModule(app.getDevice(), vertShader, nullptr);
    vkDestroyShaderModule(app.getDevice(), fragShader, nullptr);

    // Main loop
    int width = 1024, height = 768;
    VkFence waitFence = app.getOrCreateFence("CommandFence");
    VkSemaphore waitNextImage = app.getOrCreateSemaphore("WaitNextImage");
    VkSemaphore waitSubmission = app.getOrCreateSemaphore("WaitSubmission");
    VkCommandBuffer commandBuffer = app.getCommandBuffer();
    while (!app.shouldClose())
    {
        vkWaitForFences(app.getDevice(), 1, &waitFence, VK_TRUE, UINT64_MAX);
        vkResetFences(app.getDevice(), 1, &waitFence);

        uint32_t imageIndex = 0;
        VkResult result = vkAcquireNextImageKHR(app.getDevice(), app.getSwapchain(), UINT64_MAX,
                                                waitNextImage, VK_NULL_HANDLE, &imageIndex);
        switch (result)
        {
        case VK_SUBOPTIMAL_KHR:
        case VK_ERROR_OUT_OF_DATE_KHR:
            imageIndex = app.recreateSwapchain(width, height, waitNextImage); break;
        case VK_SUCCESS:
            break;
        default:
            printf("Next image: %d (RESULT = %d)\n", imageIndex, result);
            app.setClosed(); break;
        }

        VkCommandBufferBeginInfo beginInfo
        {
            VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
            nullptr, 0, nullptr
        };
        vkResetCommandBuffer(commandBuffer, 0);
        vkBeginCommandBuffer(commandBuffer, &beginInfo);
        {
            VkClearValue clearColor = { {0.2f, 0.2f, 0.4f, 1.0f} };
            VkRenderPassBeginInfo renderPassInfo
            {
                VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
                nullptr, app.getRenderPass(),
                app.getSwapchainFramebuffers()[imageIndex],
                { {0, 0}, {(uint32_t)width, (uint32_t)height} },
                1, &clearColor
            };
            vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
            {
                VkViewport viewport
                { 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f };

                VkRect2D scissor
                { {0, 0}, {(uint32_t)width, (uint32_t)height} };

                vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
                vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
                vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
                vkCmdDraw(commandBuffer, 3, 1, 0, 0);
            }
            vkCmdEndRenderPass(commandBuffer);
        }
        vkEndCommandBuffer(commandBuffer);

        app.submitAndPresent(waitNextImage, waitSubmission, waitFence, imageIndex);
        glfwPollEvents();
    }

    vkWaitForFences(app.getDevice(), 1, &waitFence, VK_TRUE, UINT64_MAX);

    vkDestroyPipeline(app.getDevice(), pipeline, nullptr);
    vkDestroyPipelineLayout(app.getDevice(), pipelineLayout, nullptr);
    return 0;
}